home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Visual Cafe 3
/
Visual Cafe 3.ISO
/
Vcafe
/
JFC.bin
/
BasicTreeUI.java
< prev
next >
Wrap
Text File
|
1998-06-30
|
67KB
|
2,253 lines
/*
* @(#)BasicTreeUI.java 1.79 98/03/05
*
* Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*
*/
package com.sun.java.swing.plaf.basic;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
import com.sun.java.swing.text.DefaultTextUI;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.util.*;
import com.sun.java.swing.plaf.ComponentUI;
import com.sun.java.swing.plaf.UIResource;
import com.sun.java.swing.plaf.TreeUI;
import com.sun.java.swing.tree.*;
/**
* The basic L&F for a hierarchical data structure.
* <p>
* Warning: serialized objects of this class will not be compatible with
* future swing releases. The current serialization support is appropriate
* for short term storage or RMI between Swing1.0 applications. It will
* not be possible to load serialized Swing1.0 objects with future releases
* of Swing. The JDK1.2 release of Swing will be the compatibility
* baseline for the serialized form of Swing objects.
*
* @version 1.79 03/05/98
* @author Rob Davis
* @author Ray Ryan
* @author Scott Violet
*/
public class BasicTreeUI extends AbstractTreeUI implements CellEditorListener,
FocusListener, KeyListener, MouseListener,
PropertyChangeListener
{
static private final int LEFT_CHILD_INDENT = 7;
static private final int RIGHT_CHILD_INDENT = 13;
static private final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
transient protected Icon collapsedIcon;
transient protected Icon expandedIcon;
/** Color used to draw hash marks. If null no hash marks will be
* drawn. */
private Color hashColor;
/** Distance between left margin and where verical dashes will be
* drawn. */
protected int leftChildIndent;
/** Distance to add to leftChildIndent to determine where cell
* contents will be drawn. */
protected int rightChildIndent;
/** Total distance that will be indented. The sum of leftChildIndent
* and rightChildIndent. */
protected int totalChildIndent;
/** Minimum preferred size. */
protected Dimension preferredMinSize;
/** Index of the row that was last selected. */
protected int lastSelectedRow;
/** Component that we're going to be drawing into. */
protected JTree tree;
/** Renderer that is being used to do the actual cell drawing. */
transient protected TreeCellRenderer currentCellRenderer;
/** Set to true if the renderer that is currently in the tree was
* created by this instance. */
protected boolean createdRenderer;
/** Editor for the tree. */
transient protected TreeCellEditor cellEditor;
/** Set to true if editor renderer that is currently in the tree was
* created by this instance. */
protected boolean createdCellEditor;
/** When editing, this will be the Component that is doing the actual
* editing. */
protected Component editingComponent;
/** Path that is being edited. */
protected TreePath editingPath;
/** Key code that is being generated for. */
protected Action repeatKeyAction;
/** Set to true while keyPressed is active. */
protected boolean isKeyDown;
/** Set to false when editing and shouldSelectCell() returns true meaning
* the node should be selected before editing, used in completeEditing. */
protected boolean stopEditingInCompleteEditing;
/** Used to paint the TreeCellRenderer. */
protected CellRendererPane rendererPane;
/** Size needed to completely display all the nodes. */
protected Dimension cPreferredSize;
/** Is the preferredSize valid? */
protected boolean validCachedPreferredSize;
/** Used for large models, listens for moved/resized events and
* updates the validCachedPreferredSize bit accordingly. */
transient protected ComponentAdapter componentListener;
/** Used for minimizing the drawing of vertical lines. */
protected Vector drawingCache;
// Cached listeners
private PropertyChangeListener propertyChangeListener;
private MouseListener mouseListener;
private FocusListener focusListener;
private KeyListener keyListener;
public static ComponentUI createUI(JComponent x) {
return new BasicTreeUI();
}
public BasicTreeUI()
{
super();
}
protected Color getHashColor()
{
return hashColor;
}
protected void setHashColor( Color color )
{
hashColor = color;
}
public void setLeftChildIndent(int newAmount)
{
leftChildIndent = newAmount;
totalChildIndent = leftChildIndent + rightChildIndent;
}
public int getLeftChildIndent()
{
return leftChildIndent;
}
public void setRightChildIndent(int newAmount)
{
rightChildIndent = newAmount;
totalChildIndent = leftChildIndent + rightChildIndent;
}
public int getRightChildIndent()
{
return rightChildIndent;
}
public void setExpandedIcon(Icon newG)
{
expandedIcon = newG;
}
public Icon getExpandedIcon()
{
return expandedIcon;
}
public void setCollapsedIcon(Icon newG)
{
collapsedIcon = newG;
}
public Icon getCollapsedIcon()
{
return collapsedIcon;
}
/**
* Updates the componentListener, if necessary.
*/
public void setLargeModel(boolean largeModel) {
if(largeModel && tree != null && componentListener == null) {
componentListener = new ComponentAdapter() {
public void componentMoved(ComponentEvent e) {
validCachedPreferredSize = false;
}
};
tree.addComponentListener(componentListener);
}
else if(!largeModel && tree != null && componentListener != null) {
tree.removeComponentListener(componentListener);
componentListener = null;
}
super.setLargeModel(largeModel);
}
public void installUI(JComponent c) {
if ( c == null ) {
throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" );
}
tree = (JTree)c;
drawingCache = new Vector();
// Data member initializations
stopEditingInCompleteEditing = true;
lastSelectedRow = -1;
setLeftChildIndent(LEFT_CHILD_INDENT);
setRightChildIndent(RIGHT_CHILD_INDENT);
cPreferredSize = new Dimension();
// Subcomponents
if ( (rendererPane = createCellRendererPane( c )) != null ) {
tree.add( rendererPane );
}
// Boilerplate install block
installDefaults( c );
installListeners( c );
installKeyboardActions( c );
// Custom install code
this.setRowHeight(tree.getRowHeight());
this.setRootVisible(tree.isRootVisible());
this.setShowsRootHandles(tree.getShowsRootHandles());
this.setModel(tree.getModel());
this.setSelectionModel(tree.getSelectionModel());
this.setLargeModel(tree.isLargeModel());
updateRenderer();
validCachedPreferredSize = false;
}
protected void installDefaults( JComponent c ) {
if(c.getBackground() == null || c.getBackground() instanceof UIResource) {
c.setBackground(UIManager.getColor("Tree.background"));
}
if(getHashColor() == null || getHashColor() instanceof UIResource) {
setHashColor(UIManager.getColor("Tree.hash"));
}
if (c.getFont() == null || c.getFont() instanceof UIResource)
c.setFont( UIManager.getFont("Tree.font") );
setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) );
setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) );
}
protected void installListeners( JComponent c ) {
if ( (propertyChangeListener = createPropertyChangeListener( c )) != null ) {
c.addPropertyChangeListener( propertyChangeListener );
}
if ( (mouseListener = createMouseListener( c )) != null ) {
c.addMouseListener( mouseListener );
}
if ( (focusListener = createFocusListener( c )) != null ) {
c.addFocusListener( focusListener );
}
if ( (keyListener = createKeyListener( c )) != null ) {
c.addKeyListener( keyListener );
}
}
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return this;
}
protected MouseListener createMouseListener( JComponent c ) {
return this;
}
protected FocusListener createFocusListener( JComponent c ) {
return this;
}
protected KeyListener createKeyListener( JComponent c ) {
return this;
}
protected CellRendererPane createCellRendererPane( JComponent c ) {
return new CellRendererPane();
}
public void uninstallUI(JComponent c) {
completeEditing();
tree.remove(rendererPane);
uninstallDefaults( c );
uninstallListeners( c );
uninstallKeyboardActions( c );
checkConsistency();
// These should be the last items in this method. DON'T add after this.
if(createdRenderer) {
tree.setCellRenderer(null);
}
if(createdCellEditor) {
tree.setCellEditor(null);
}
rendererPane = null;
componentListener = null;
propertyChangeListener = null;
mouseListener = null;
focusListener = null;
keyListener = null;
setSelectionModel(null);
setModel(null);
tree = null;
drawingCache = null;
}
protected void uninstallDefaults( JComponent c ) {
}
protected void uninstallListeners( JComponent c ) {
if(componentListener != null) {
tree.removeComponentListener(componentListener);
}
if ( propertyChangeListener != null ) {
c.removePropertyChangeListener( propertyChangeListener );
}
if ( mouseListener!= null ) {
c.removeMouseListener( mouseListener );
}
if ( focusListener!= null ) {
c.removeFocusListener( focusListener );
}
if ( keyListener!= null ) {
c.removeKeyListener( keyListener );
}
}
/**
* Based on the value has changed will message the appropriate
* method. Which is one of treeRendererChanged, setModel,
* setRootVisible, setShowsRootHandles, or setRowHeight.
*/
public void propertyChange(PropertyChangeEvent event) {
if(event.getSource() == tree) {
String changeName = event.getPropertyName();
completeEditing();
if(changeName.equals(JTree.CELL_RENDERER_PROPERTY)) {
updateRenderer();
tree.repaint();
}
else if(changeName.equals(JTree.TREE_MODEL_PROPERTY))
setModel((TreeModel)event.getNewValue());
else if(changeName.equals(JTree.ROOT_VISIBLE_PROPERTY)) {
setRootVisible(((Boolean)event.getNewValue()).
booleanValue());
tree.repaint();
}
else if(changeName.equals(JTree.SHOWS_ROOT_HANDLES_PROPERTY)) {
setShowsRootHandles(((Boolean)event.getNewValue()).
booleanValue());
tree.repaint();
}
else if(changeName.equals(JTree.ROW_HEIGHT_PROPERTY))
setRowHeight(((Integer)event.getNewValue()).
intValue());
else if(changeName.equals(JTree.CELL_EDITOR_PROPERTY)) {
updateCellEditor();
}
else if(changeName.equals(JTree.EDITABLE_PROPERTY)) {
updateCellEditor();
}
else if(changeName.equals(JTree.LARGE_MODEL_PROPERTY)) {
setLargeModel(tree.isLargeModel());
}
else if(changeName.equals(JTree.SELECTION_MODEL_PROPERTY)) {
setSelectionModel(tree.getSelectionModel());
}
}
else if(event.getSource() == treeSelectionModel)
treeSelectionModel.resetRowSelection();
}
protected void installKeyboardActions( JComponent c )
{
c.registerKeyboardAction(new TreeIncrementAction(-1, "UP"),
KeyStroke.getKeyStroke(KeyEvent.VK_UP,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreeIncrementAction(1, "DOWN"),
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreeTraverseAction(1, "RIGHT"),
KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreeTraverseAction(-1, "LEFT"),
KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreePageAction(-1, "P_UP"),
KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreePageAction(1, "P_DOWN"),
KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreeHomeAction(-1, "HOME"),
KeyStroke.getKeyStroke(KeyEvent.VK_HOME,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreeHomeAction(1, "END"),
KeyStroke.getKeyStroke(KeyEvent.VK_END,0),
JComponent.WHEN_FOCUSED);
c.registerKeyboardAction(new TreeToggleAction("TOGGLE"),
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0),
JComponent.WHEN_FOCUSED);
}
protected void uninstallKeyboardActions( JComponent c )
{
c.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_UP,
0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,
0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,
0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke
(KeyEvent.VK_RIGHT, 0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.
VK_PAGE_UP, 0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke
(KeyEvent.VK_PAGE_DOWN, 0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.
VK_HOME, 0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke
(KeyEvent.VK_END, 0));
c.unregisterKeyboardAction(KeyStroke.getKeyStroke
(KeyEvent.VK_ENTER, 0));
}
/**
* Updates the cellEditor based on the editability of the JTree that
* we're contained in. If the tree is editable but doesn't have a
* cellEditor, a basic one will be used.
*/
protected void updateCellEditor() {
TreeCellEditor newEditor;
completeEditing();
if(tree == null)
newEditor = null;
else {
if(tree.isEditable()) {
newEditor = tree.getCellEditor();
if(newEditor == null) {
newEditor = createDefaultCellEditor();
if(newEditor != null) {
tree.setCellEditor(newEditor);
createdCellEditor = true;
}
}
}
else
newEditor = null;
}
if(newEditor != cellEditor) {
if(cellEditor != null)
cellEditor.removeCellEditorListener(this);
cellEditor = newEditor;
if(newEditor != null)
newEditor.addCellEditorListener(this);
createdCellEditor = false;
}
}
/**
* Messaged from the tree we're in when the renderer has changed.
* Updates the size if necessary.
*/
protected void updateRenderer() {
TreeCellRenderer newCellRenderer;
newCellRenderer = tree.getCellRenderer();
if(newCellRenderer == null && tree != null) {
tree.setCellRenderer(createDefaultCellRenderer());
createdRenderer = true;
}
else
{
createdRenderer = false;
currentCellRenderer = newCellRenderer;
if(!largeModel)
this.updateNodeSizes(true);
if(createdCellEditor && tree != null) {
tree.setCellEditor(null);
}
}
updateCellEditor();
}
/** Checks to insure that the all the sizes of the nodes are valid,
* and if there isn't a valid node size, as determined by
* updateNodeSizes in our superclass, than <b>updateNodeSizes()</b>
* is messaged.
*/
public boolean checkConsistency()
{
if(tree != null && updateNodeSizes)
{
if(!largeModel)
this.updateNodeSizes(true);
return true;
}
return false;
}
public void updateNodeSizes(boolean updateAll) {
super.updateNodeSizes(updateAll);
validCachedPreferredSize = false;
}
/**
* Creates a default cell editor.
*/
protected TreeCellEditor createDefaultCellEditor() {
if(currentCellRenderer != null &&
(currentCellRenderer instanceof BasicTreeCellRenderer))
return new BasicTreeCellEditor((BasicTreeCellRenderer)
currentCellRenderer);
return new BasicTreeCellEditor(null);
}
/**
* Returns the default cell renderer that is used to do the
* stamping of each node.
*/
public TreeCellRenderer createDefaultCellRenderer() {
return new BasicTreeCellRenderer();
}
/**
* Returns the x origin of the given node, which is based on whether
* or not we're showing handles and the visible level of the node
* multiplied by the right and left indent factor.
*/
public int getXOriginOfNode(VisibleTreeNode node)
{
int visibleLevel = node.getVisibleLevel();
if(this.getShowsRootHandles())
visibleLevel++;
return (totalChildIndent * visibleLevel);
}
/**
* Messages the tree to configure the cell, and returns the
* the size of the component.
*/
public Dimension getSizeOfNode(VisibleTreeNode node, int index)
{
Component aComponent = null;
if(currentCellRenderer != null) {
aComponent = currentCellRenderer.getTreeCellRendererComponent
(tree, node.getValue(), false, node.isExpanded(),
node.isLeaf(), index, false);
if(tree != null) {
/* Notice how it is never removed. This is OK according to
hans. */
rendererPane.add(aComponent);
aComponent.validate();
return aComponent.getPreferredSize();
}
return aComponent.getPreferredSize();
}
return new Dimension(0, 0);
}
/**
* Returns the rectangle needed to draw the passed in row. This messages
* getLargeBoundsOf(parent, row, childUserObject, null, null) for the
* return value.
*/
protected Rectangle getLargeBoundsOf(LargeTreeModelNode parent, int row,
Object childUserObject) {
return getLargeBoundsOf(parent, row, childUserObject, null, null);
}
/**
* Returns the rectangle needed to draw the passed in row. If Component
* is non-null the size is taken from the component. If placeIn is
* non-null the returned Rectangle will be placeIn[0].
*/
protected Rectangle getLargeBoundsOf(LargeTreeModelNode parent, int row,
Object childUserObject,
Component component,
Rectangle[] placeIn) {
Rectangle retRectangle;
int visibleLevel;
if(placeIn == null)
retRectangle = new Rectangle();
else
retRectangle = placeIn[0];
if(parent == null)
visibleLevel = 0;
else {
visibleLevel = parent.getVisibleLevel() + 1;
if(this.getShowsRootHandles())
visibleLevel++;
}
retRectangle.x = (totalChildIndent * visibleLevel);
retRectangle.y = row * rowHeight;
retRectangle.height = rowHeight;
if(component == null) {
if(currentCellRenderer != null) {
Component aComponent;
aComponent = currentCellRenderer.getTreeCellRendererComponent
(tree, childUserObject, false, false, false, row, false);
if(tree != null) {
/* Notice how it is never removed. This is OK according to
hans. */
rendererPane.add(aComponent);
aComponent.validate();
}
Dimension size = aComponent.getPreferredSize();
retRectangle.width = size.width;
}
else
retRectangle.width = 0;
}
else {
Dimension size = component.getPreferredSize();
retRectangle.width = size.width;
}
return retRectangle;
}
/**
* Updates the <code>cPreferredSize</code> instance variable,
* which is returned from <code>getPreferredSize()</code>.<p>
* If <code>largeModel</code> is true, the width is determined
* from only the visible rows, otherwise the width is determined
* from <code>getMaxNodeWidth</code>.
*/
protected void updateCachedPreferredSize() {
if(!largeModel) {
int rowCount = this.getRowCount();
int suggestedHeight;
if(rowCount > 0) {
if(!isFixedRowHeight()) {
VisibleTreeNode lastNode = this.getNode(rowCount - 1);
suggestedHeight = lastNode.getYOrigin() +
lastNode.getPreferredSize().height;
}
else
suggestedHeight = this.getRowHeight() * rowCount;
}
else
suggestedHeight = 0;
cPreferredSize.height = suggestedHeight;
cPreferredSize.width = this.getMaxNodeWidth();
}
else if(tree != null) {
/* NOTE: The determinination of the row width could be made a
little snappier if made recusive... Just a little though. */
int firstRow, lastRow;
int minWidth = 0;
Rectangle visRect;
visRect = tree.getVisibleRect();
firstRow = getRowContainingYLocation(visRect.y);
lastRow = getRowContainingYLocation(visRect.y + visRect.height);
if(firstRow != -1 && firstRow != -1) {
Rectangle rowBounds;
while(firstRow <= lastRow) {
rowBounds = getRowBounds(firstRow);
if(rowBounds != null)
minWidth = Math.max(minWidth,
rowBounds.x + rowBounds.width);
firstRow++;
}
}
cPreferredSize.width = minWidth;
cPreferredSize.height = getRowCount() * getRowHeight();
}
else
cPreferredSize.width = cPreferredSize.height = 0;
validCachedPreferredSize = true;
}
/**
* Messaged whenever nodes are added/removed from the visible list,
* or the height/width of a node changes. This updates the size
* of the JTree we're drawing for based on the dimension
* returned from <b>getPreferredSize</b>.
*/
public void visibleNodesChanged() {
if(tree != null) {
validCachedPreferredSize = false;
tree.treeDidChange();
}
}
/**
* Messaged from the VisibleTreeNode after it has been expanded.
*/
protected void pathWasExpanded(TreePath path) {
if(tree != null) {
tree.fireTreeExpanded(path);
}
}
/**
* Messaged from the VisibleTreeNode after it has collapsed.
*/
protected void pathWasCollapsed(TreePath path) {
if(tree != null) {
tree.fireTreeCollapsed(path);
}
}
/**
* Ensures that the rows identified by beginRow through endRow are
* visible.
*/
public void ensureRowsAreVisible(int beginRow, int endRow) {
if(tree != null && beginRow >= 0 && endRow < getRowCount()) {
if(beginRow == endRow)
tree.scrollRectToVisible(getRowBounds(beginRow));
else {
Rectangle beginRect = getRowBounds(beginRow);
Rectangle testRect = beginRect;
int beginY = beginRect.y;
int maxX = beginRect.x + beginRect.width;
int minX = beginRect.x;
int availHeight = tree.getVisibleRect().height;
for(int counter = beginRow + 1; counter < endRow; counter++) {
testRect = getRowBounds(counter);
if((testRect.width + testRect.x) > maxX)
maxX = testRect.width + testRect.x;
if(testRect.x < minX)
minX = testRect.x;
if((testRect.y + testRect.height - beginY) > availHeight)
counter = endRow;
}
tree.scrollRectToVisible(new Rectangle(minX, beginY,
maxX - minX,
testRect.y + testRect.height-
beginY));
}
}
}
/**
* Makes sure all the path components in path are expanded (accept
* for the last path component) and tries to scroll the resulting path
* to be visible (the scrolling will only work if the JTree is
* contained in a JScrollPane).
*/
public void scrollPathToVisible(TreePath path) {
makeVisible(path);
Rectangle rect = getPathBounds(path);
if(rect != null && tree != null)
tree.scrollRectToVisible(rect);
}
/**
* Scrolls the item identified by row to be visible. This will
* only work if the JTree is contained in a JSrollPane.
*/
public void scrollRowToVisible(int row) {
Rectangle rect = getRowBounds(row);
if(rect != null && tree != null)
tree.scrollRectToVisible(rect);
}
/**
* Return currentCellRenderer, which will either be the trees
* renderer, or defaultCellRenderer, which ever wasn't null.
* currentCellRenderer is set as part of checkConsistency().
*/
public TreeCellRenderer getCellRenderer() {
return currentCellRenderer;
}
/**
* Creates a new instance of BasicLargeTreeModelNode.
*/
protected LargeTreeModelNode createExpandedNodeForValue(Object value,
int childIndex) {
return new BasicLargeTreeModelNode(this, value, childIndex);
}
public void paint(Graphics g, JComponent c) {
if (tree != c) {
throw new InternalError("incorrect component");
}
checkConsistency();
TreeCellRenderer renderer = getCellRenderer();
final int totalIndent = totalChildIndent;
Color bColor = tree.getBackground();
Dimension cSize = c.getSize();
Rectangle paintBounds = g.getClipBounds();
int beginRow, endRow;
beginRow = getRowContainingYLocation(paintBounds.y);
endRow = getRowContainingYLocation(paintBounds.y +
paintBounds.height);
boolean shouldPaint = (beginRow > -1 && endRow > -1);
if(shouldPaint && largeModel) {
int[] rowCounter = new int[1];
if(isRootVisible()) {
rowCounter[0] = 0;
((BasicLargeTreeModelNode)largeRoot).paintAll
(new BasicTreeUIPaintInfo(this, g), rowCounter,
beginRow, endRow, 0, this);
}
else {
rowCounter[0] = -1;
((BasicLargeTreeModelNode)largeRoot).paintAll
(new BasicTreeUIPaintInfo(this, g), rowCounter,
beginRow, endRow, -1, this);
}
}
else if(shouldPaint) {
g.setColor( getHashColor() );
// Draw the lines, knobs, and rows
VisibleTreeNode childNode = getNode( beginRow );
VisibleTreeNode parentNode = (VisibleTreeNode)childNode.getParent();
// Find each parent and have them draw a line to their last child
while ( parentNode != null )
{
VisibleTreeNode lastChild = (VisibleTreeNode)parentNode.getLastChild();
drawVerticalPartOfLeg( g, c, parentNode.getVisibleLevel(), getNodeY( parentNode ),
getNodeY( lastChild ), getNodeHeight( parentNode ),
getNodeHeight( lastChild ) );
drawingCache.addElement( parentNode );
parentNode = (VisibleTreeNode)parentNode.getParent();
}
for ( int row = beginRow; row <= endRow; ++row )
{
childNode = getNode( row );
parentNode = (VisibleTreeNode)childNode.getParent();
if ( parentNode != null && !drawingCache.contains( parentNode ) )
{
VisibleTreeNode lastChild = (VisibleTreeNode)parentNode.getLastChild();
drawVerticalPartOfLeg( g, c, parentNode.getVisibleLevel(), getNodeY( parentNode ),
getNodeY( lastChild ), getNodeHeight( parentNode ),
getNodeHeight( lastChild ) );
drawingCache.addElement( parentNode );
}
if ( parentNode != null )
{
drawHorizontalPartOfLeg( g, c,
getNodeY( childNode ) + getNodeHeight( childNode ) / 2,
getNodeX( parentNode ) + 8,
getNodeX( childNode ) );
}
int childX = getNodeX( childNode );
int childY = getNodeY( childNode );
int childRowHeight = getNodeHeight( childNode );
// Paints the 'knobs' like the + and - squares for collapsing and expanding.
if ( shouldPaintExpandControl( parentNode, childNode ) )
{
paintExpandControl( g, c, parentNode, childNode,
childX, childY,
childRowHeight, row );
}
// Paints the main contents of the row
paintRow( g, c, parentNode, childNode,
childX, childY,
childRowHeight, row );
}
drawingCache.removeAllElements();
}
}
protected boolean shouldPaintExpandControl( VisibleTreeNode parentNode, VisibleTreeNode childNode )
{
boolean result = true;
if ( (childNode.getLevel() == 0 && !getShowsRootHandles()) ||
(childNode.getLevel() == 1 && !isRootVisible() && !getShowsRootHandles()) )
{
result = false;
}
return result;
}
protected int getNodeX( VisibleTreeNode node )
{
int levelOffset = getShowsRootHandles() ? 1 : 0;
return (node.getVisibleLevel() + levelOffset) * totalChildIndent;
}
protected int getNodeY( VisibleTreeNode node )
{
return node.getYOrigin();
}
protected int getNodeHeight( VisibleTreeNode node )
{
return isFixedRowHeight() ? getRowHeight() : node.getPreferredSize().height;
}
/*
protected void drawVerticalPartOfLeg( Graphics g, JComponent c,
VisibleTreeNode parentNode, VisibleTreeNode childNode )
{
int parentX = getNodeX( parentNode );
int lineX = parentX + 8;
Rectangle clipBounds = g.getClipBounds();
int clipLeft = clipBounds.x;
int clipRight = clipBounds.x + (clipBounds.width - 1);
if ( lineX > clipLeft && lineX < clipRight )
{
int clipTop = clipBounds.y;
int clipBottom = clipBounds.y + (clipBounds.height - 1);
int parentY = getNodeY( parentNode );
int parentRowHeight = getNodeHeight( parentNode );
int childY = getNodeY( childNode );
int childRowHeight = getNodeHeight( childNode );
int top = Math.max( parentY + (parentRowHeight - 1) + getVerticalLegBuffer(), clipTop );
int bottom = Math.min( childY + (childRowHeight / 2), clipBottom );
g.setColor( getHashColor() );
drawVerticalLine( g, c, lineX, top, bottom );
}
}
*/
public void drawVerticalPartOfLeg( Graphics g, JComponent c, int depth, int parentY, int childY,
int parentRowHeight, int childRowHeight ) {
int levelOffset = getShowsRootHandles() ? 1 : 0;
int lineX = ((depth + levelOffset) * totalChildIndent) + 8;
Rectangle clipBounds = g.getClipBounds();
int clipLeft = clipBounds.x;
int clipRight = clipBounds.x + (clipBounds.width - 1);
if ( lineX > clipLeft && lineX < clipRight )
{
int clipTop = clipBounds.y;
int clipBottom = clipBounds.y + clipBounds.height;
int top = Math.max( parentY + parentRowHeight + getVerticalLegBuffer(), clipTop );
int bottom = Math.min( childY + (childRowHeight / 2), clipBottom );
g.setColor( getHashColor() );
drawVerticalLine( g, c, lineX, top, bottom );
}
}
/**
* The vertical element of legs between nodes starts at the bottom of the
* parent node by default. This method makes the leg start below that.
*/
protected int getVerticalLegBuffer()
{
return 0;
}
/**
* The horizontal element of legs between nodes starts at the right of the
* left-hand side of the child node by default. This method makes the leg end before that.
*/
protected int getHorizontalLegBuffer()
{
return 0;
}
protected void drawVerticalLine( Graphics g, JComponent c, int x, int top, int bottom )
{
g.drawLine( x, top, x, bottom );
}
/*
protected void drawHorizontalPartOfLeg( Graphics g, JComponent c,
VisibleTreeNode parentNode, VisibleTreeNode childNode )
{
Rectangle clipBounds = g.getClipBounds();
int clipLeft = clipBounds.x;
int clipRight = clipBounds.x + (clipBounds.width - 1);
int clipTop = clipBounds.y;
int clipBottom = clipBounds.y + (clipBounds.height - 1);
int childY = getNodeY( childNode );
int childRowHeight = getNodeHeight( childNode );
int lineY = childY + (childRowHeight / 2);
int parentX = getNodeX( parentNode );
int childX = getNodeX( childNode );
int left = parentX + 8;
int right = childX - getHorizontalLegBuffer();
if ( lineY > clipTop && lineY < clipBottom && right > clipLeft && left < clipRight )
{
left = Math.max( left, clipLeft );
right = Math.min( right, clipRight );
g.setColor( getHashColor() );
drawHorizontalLine(g, c, lineY, left, right );
}
}
*/
public void drawHorizontalPartOfLeg( Graphics g, JComponent c, int lineY, int leftX, int rightX )
{
Rectangle clipBounds = g.getClipBounds();
int clipLeft = clipBounds.x;
int clipRight = clipBounds.x + (clipBounds.width - 1);
int clipTop = clipBounds.y;
int clipBottom = clipBounds.y + (clipBounds.height - 1);
rightX -= getHorizontalLegBuffer();
if ( lineY > clipTop && lineY < clipBottom && rightX > clipLeft && leftX < clipRight )
{
leftX = Math.max( leftX, clipLeft );
rightX = Math.min( rightX, clipRight );
g.setColor( getHashColor() );
drawHorizontalLine(g, c, lineY, leftX, rightX );
}
}
protected void drawHorizontalLine( Graphics g, JComponent c, int y, int left, int right )
{
g.drawLine( left, y, right, y );
}
protected void paintRow( Graphics g, JComponent c,
VisibleTreeNode parentNode, VisibleTreeNode childNode,
int childX, int childY,
int childRowHeight, int row )
{
int leadIndex = -1;
if(tree.hasFocus())
{
leadIndex = tree.getLeadSelectionRow();
}
Component component = getCellRenderer().getTreeCellRendererComponent(tree,
childNode.getValue(), isSelectedIndex(row),
childNode.isExpanded(),childNode.isLeaf(), row,
(leadIndex == row));
rendererPane.paintComponent(g, component,tree, childX, childY,
childNode.getPreferredSize().width,
childRowHeight, true);
}
protected void paintExpandControl( Graphics g, JComponent c,
VisibleTreeNode parentNode, VisibleTreeNode childNode,
int childX, int childY,
int childRowHeight, int row )
{
// Draw icons if not a leaf and either hasn't been loaded,
// or the model child count is > 0.
if (!childNode.isLeaf() && (!childNode.hasBeenExpanded() ||
childNode.getModelChildCount() > 0))
{
int middleXOfKnob = childX - (getRightChildIndent() - 1);
int middleYOfKnob = childY + (childRowHeight / 2);
if (childNode.isExpanded())
{
Icon expandedIcon = getExpandedIcon();
if(expandedIcon != null)
drawCentered( c, g, expandedIcon, middleXOfKnob, middleYOfKnob );
}
else
{
Icon collapsedIcon = getCollapsedIcon();
if(collapsedIcon != null)
drawCentered( c, g, collapsedIcon, middleXOfKnob, middleYOfKnob );
}
}
}
// Draws the icon centered at (x,y)
protected void drawCentered(Component c, Graphics graphics, Icon icon, int x, int y) {
icon.paintIcon(c, graphics, x - icon.getIconWidth()/2, y - icon.getIconHeight()/2);
}
// This method is slow -- revisit when Java2D is ready.
// assumes x1 <= x2
protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){
// Drawing only even coordinates helps join line segments so they
// appear as one line. This can be defeated by translating the
// Graphics by an odd amount.
x1 += (x1 % 2);
for (int x = x1; x <= x2; x+=2) {
g.drawLine(x, y, x, y);
}
}
// This method is slow -- revisit when Java2D is ready.
// assumes y1 <= y2
protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) {
// Drawing only even coordinates helps join line segments so they
// appear as one line. This can be defeated by translating the
// Graphics by an odd amount.
y1 += (y1 % 2);
for (int y = y1; y <= y2; y+=2) {
g.drawLine(x, y, x, y);
}
}
/** Sets the preferred minimum size.
*/
public void setPreferredMinSize(Dimension newSize)
{
preferredMinSize = newSize;
}
/** Returns the minimum preferred size.
*/
public Dimension getPreferredMinSize()
{
return preferredMinSize;
}
/** Returns the preferred size to properly display the tree,
* this is a cover method for getPreferredSize(c, false).
*/
public Dimension getPreferredSize(JComponent c) {
return getPreferredSize(c, true);
}
/** Returns the preferred size to represent the tree in
* <I>c</I>. If <I>checkConsistancy</I> is true
* <b>checkConsistancy</b> is messaged first.
*/
public Dimension getPreferredSize(JComponent c,
boolean checkConsistancy) {
Dimension pSize = this.getPreferredMinSize();
if(checkConsistancy)
this.checkConsistency();
if(!validCachedPreferredSize)
updateCachedPreferredSize();
if(tree != null)
{
if(pSize != null)
return new Dimension(Math.max(pSize.width,
cPreferredSize.width),
Math.max(pSize.height, cPreferredSize.height));
return new Dimension(cPreferredSize.width, cPreferredSize.height);
}
else if(pSize != null)
return pSize;
else
return new Dimension(0, 0);
}
/**
* Returns the minimum size for this component. Which will be
* the min preferred size or 0, 0.
*/
public Dimension getMinimumSize(JComponent c) {
if(this.getPreferredMinSize() != null)
return this.getPreferredMinSize();
return new Dimension(0, 0);
}
/**
* Returns the maximum size for this component, which will be the
* preferred size if the instance is currently in a JTree, or 0, 0.
*/
public Dimension getMaximumSize(JComponent c) {
if(tree != null)
return getPreferredSize(tree);
if(this.getPreferredMinSize() != null)
return this.getPreferredMinSize();
return new Dimension(0, 0);
}
/**
* Messaged when the selection changes in the tree we're displaying
* for. Stops editing, messages super and displays the changed paths.
*/
public void valueChanged(TreeSelectionEvent event) {
completeEditing();
super.valueChanged(event);
lastSelectedRow = getMinSelectionRow();
TreePath[] changedPaths = event.getPaths();
Rectangle nodeBounds;
Rectangle visRect = tree.getVisibleRect();
boolean paintPaths = true;
int nWidth = tree.getSize().width;
if(changedPaths != null) {
int counter, maxCounter = changedPaths.length;
if(maxCounter > 4) {
tree.repaint();
paintPaths = false;
}
else {
VisibleTreeNode aNode;
for (counter = 0; counter < maxCounter; counter++) {
nodeBounds = getPathBounds(changedPaths[counter]);
if(nodeBounds != null && visRect.intersects(nodeBounds))
tree.repaint(0, nodeBounds.y, nWidth,
nodeBounds.height);
}
}
}
if(paintPaths) {
nodeBounds = getPathBounds(event.getOldLeadSelectionPath());
if(nodeBounds != null && visRect.intersects(nodeBounds))
tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
nodeBounds = getPathBounds(event.getNewLeadSelectionPath());
if(nodeBounds != null && visRect.intersects(nodeBounds))
tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
}
}
/**
* Stops the editing session by messaging stopCellEditing(false).
*/
public void editingStopped(ChangeEvent e) {
completeEditing(false, false, true);
}
/**
* Stops the editing session by messaging cancelEditing(false).
*/
public void editingCanceled(ChangeEvent e) {
completeEditing(false, false, false);
}
/**
* Returns true if the tree is being edited. The item that is being
* edited can be returned by getSelectionPath().
*/
public boolean isEditing() {
return (editingComponent != null);
}
/**
* Stops the current editing session, returns true if the tree is
* current editing and the editor returns true from stopCellEditing().
*/
public boolean stopEditing() {
if(editingComponent != null && cellEditor.stopCellEditing()) {
completeEditing(false, false, true);
return true;
}
return false;
}
/**
* Cancels the current editing session.
*/
public void cancelEditing() {
if(editingComponent != null) {
completeEditing(false, true, false);
}
}
/**
* Messages to stop the editing session. If the UI the receiver
* is providing the look and feel for returns true from
* <code>getInvokesStopCellEditing</code>, stopCellEditing will
* invoked on the current editor. Then completeEditing will
* be messaged with false, true, false to cancel any lingering
* editing.
*/
protected void completeEditing() {
/* If should invoke stopCellEditing, try that */
if(tree.getInvokesStopCellEditing() &&
stopEditingInCompleteEditing && editingComponent != null) {
cellEditor.stopCellEditing();
}
/* Invoke cancelCellEditing, this will do nothing if stopCellEditing
was succesful. */
completeEditing(false, true, false);
}
/**
* Stops the editing session. If messageStop is true the editor
* is messaged with stopEditing, if messageCancel is true the
* editor is messaged with cancelEditing. If messageTree is true
* the treeModel is messaged with valueForPathChanged.
*/
protected void completeEditing(boolean messageStop,
boolean messageCancel,
boolean messageTree) {
if(stopEditingInCompleteEditing && editingComponent != null) {
Component oldComponent = editingComponent;
TreePath oldPath = editingPath;
TreeCellEditor oldEditor = cellEditor;
Object newValue = oldEditor.getCellEditorValue();
Rectangle editingBounds = getPathBounds(editingPath);
editingComponent = null;
editingPath = null;
if(messageStop)
oldEditor.stopCellEditing();
else if(messageCancel)
oldEditor.cancelCellEditing();
tree.remove(oldComponent);
editingBounds.x = 0;
editingBounds.width = tree.getSize().width;
tree.repaint(editingBounds);
if(messageTree)
treeModel.valueForPathChanged(oldPath, newValue);
}
}
/**
* Selects the last item in path and tries to edit it. Editing will
* fail if the CellEditor won't allow it for the selected item.
*/
public void startEditingAtPath(TreePath path) {
VisibleTreeNode aNode;
scrollPathToVisible(path);
if(path != null && isVisible(path))
startEditing(path, null);
}
/**
* Will start editing for node if there is a cellEditor and
* shouldSelectCell returns true.<p>
* This assumes that path is valid and visible.
*/
protected boolean startEditing(TreePath path, MouseEvent event) {
completeEditing();
if(cellEditor != null && tree.isPathEditable(path)) {
int row = getRowForPath(path);
editingComponent = cellEditor.getTreeCellEditorComponent
(tree, path.getLastPathComponent(), isPathSelected(path),
isExpanded(path), treeModel.isLeaf
(path.getLastPathComponent()), row);
if(cellEditor.isCellEditable(event)) {
Rectangle nodeBounds = getPathBounds(path);
if(editingComponent.getFont() == null) {
editingComponent.setFont(tree.getFont());
}
Dimension editorSize = editingComponent.getPreferredSize();
tree.add(editingComponent);
editingComponent.setBounds(nodeBounds.x, nodeBounds.y,
nodeBounds.width,
nodeBounds.height);
editingPath = path;
editingComponent.validate();
tree.paintImmediately(nodeBounds.x, nodeBounds.y,
nodeBounds.width, nodeBounds.height);
if(cellEditor.shouldSelectCell(event)) {
stopEditingInCompleteEditing = false;
try {
tree.setSelectionRow(row);
} catch (Exception e) {
System.out.println("Editing exception: " + e);
}
stopEditingInCompleteEditing = true;
}
if(event != null && event instanceof MouseEvent) {
/* Find the component that will get forwarded all the
mouse events until mouseReleased. */
Point componentPoint = SwingUtilities.convertPoint
(tree, new Point(event.getX(), event.getY()),
editingComponent);
/* Create an instance of BasicTreeMouseListener to handle
passing the mouse/motion events to the necessary
component. */
new BasicTreeMouseListener(tree, SwingUtilities
.getDeepestComponentAt(editingComponent,
componentPoint.x, componentPoint.y),
event);
}
return true;
}
else
editingComponent = null;
}
return false;
}
/**
* Returns the path to the element that is being edited.
*/
public TreePath getEditingPath() {
return editingPath;
}
/**
* Invoked when the mouse has been clicked on a component.
*/
public void mouseClicked(MouseEvent e) {
}
/**
* Invoked when a mouse button has been pressed on a component.
*/
public void mousePressed(MouseEvent e) {
int row;
if(tree != null && tree.isEnabled())
tree.requestFocus();
checkConsistency();
if(tree != null && tree.isEnabled()) {
row = this.getRowContainingYLocation(e.getY());
/* getRowContainingYLocation will return the last row,
even if outside of it, this insures it isn't beyond the
last row. */
if(row != -1) {
Rectangle rect = getRowBounds(row);
if(e.getY() > (rect.y + rect.height)) {
row = -1;
}
}
}
else
row = -1;
if (/*SwingUtils.isLeftMouseButton(e) && */ row > -1 &&
row < this.getRowCount()) {
int rowLevel;
int childIndex = -1;
LargeTreeModelNode eNode = null;
VisibleTreeNode node = null;
if(!largeModel) {
node = this.getNode(row);
rowLevel = node.getVisibleLevel();
}
else {
if(row == 0 && isRootVisible()) {
eNode = largeRoot;
rowLevel = 0;
}
else {
int[] cIndex = new int[1];
eNode = getLargeParentAndChildIndexOfRow(row, cIndex);
rowLevel = eNode.getVisibleLevel() + 1;
childIndex = cIndex[0];
}
}
checkForClickInExpandControl( node, eNode, childIndex, row, rowLevel, e.getX(), e.getY() );
int x = e.getX();
// Perhaps they clicked the cell itself. If so,
// select it.
int cellLeftX;
if(this.getShowsRootHandles())
cellLeftX = totalChildIndent * (rowLevel + 1);
else
cellLeftX = totalChildIndent * rowLevel;
if (x > cellLeftX) {
int cellWidth;
if(!largeModel)
cellWidth = node.getPreferredSize().width;
else
cellWidth = getRowBounds(row).width;
if (x <= cellWidth + cellLeftX &&
!startEditing(getPathForRow(row), e)) {
int originalSelectedIndex;
originalSelectedIndex = lastSelectedRow;
if(originalSelectedIndex >= this.getRowCount() ||
!this.isSelectedIndex(originalSelectedIndex))
originalSelectedIndex = -1;
/* Control toggles just this node. */
if(e.isControlDown()) {
if(isSelectedIndex(row))
tree.removeSelectionInterval(row, row);
else
tree.addSelectionInterval(row, row);
if(originalSelectedIndex != -1)
lastSelectedRow = originalSelectedIndex;
}
/* Shift adjusts from the anchor point. */
else if(e.isShiftDown())
{
if(originalSelectedIndex == -1)
tree.addSelectionInterval(row, row);
else
{
if(row < originalSelectedIndex)
tree.setSelectionInterval(row,
originalSelectedIndex);
else
tree.setSelectionInterval
(originalSelectedIndex, row);
lastSelectedRow = originalSelectedIndex;
}
if(originalSelectedIndex != -1)
lastSelectedRow = originalSelectedIndex;
}
/* Otherwise set the selection to just this interval. */
else {
tree.setSelectionInterval(row, row);
if(e.getClickCount() == 2) {
if(!largeModel) {
node.toggleExpanded();
ensureRowsAreVisible(row, row + Math.min
(10, node.visibleChildCount()));
}
else {
if(childIndex == -1) {
eNode.toggleExpanded();
ensureRowsAreVisible(row, row + Math.min
(10, eNode.getTotalChildCount()));
}
else {
if(!isExpanded(row))
expandRow(row);
else
collapseRow(row);
LargeTreeModelNode newNode;
newNode = getLargeTreeModelNodeForRow
(row, false);
if(newNode != null)
ensureRowsAreVisible
(row, row + Math.min
(10, newNode.getTotalChildCount()));
else
ensureRowsAreVisible(row, row);
}
}
}
}
}
}
// PENDING: Should select on mouse down, start a drag if
// the mouse moves, and fire selection change notice on
// mouse up. That is, the explorer highlights on mouse
// down, but doesn't update the pane to the right (and
// open the folder icon) until mouse up.
}
}
protected void checkForClickInExpandControl( VisibleTreeNode node, LargeTreeModelNode eNode,
int childIndex, int row,
int rowLevel, int mouseX, int mouseY ) {
if ( clickedInExpandControl( node, eNode, row, rowLevel, mouseX, mouseY ) )
{
handleExpandControlClick( node, eNode, childIndex, row );
}
}
protected boolean clickedInExpandControl( VisibleTreeNode node, LargeTreeModelNode eNode,
int row, int rowLevel, int mouseX, int mouseY )
{
if ( node != null && node.isLeaf() )
{
return false;
}
int boxWidth;
if(getExpandedIcon() != null)
boxWidth = getExpandedIcon().getIconWidth();
else
boxWidth = 8;
int boxLeftX;
if(this.getShowsRootHandles())
boxLeftX = ((rowLevel * totalChildIndent) +
getLeftChildIndent()) - boxWidth/2;
else
boxLeftX = (((rowLevel - 1) * totalChildIndent) +
getLeftChildIndent()) - boxWidth/2;
int boxRightX = boxLeftX + boxWidth;
return mouseX >= boxLeftX && mouseX <= boxRightX;
}
public void handleExpandControlClick( VisibleTreeNode node, LargeTreeModelNode eNode,
int childIndex, int row ) {
if(!largeModel) {
node.toggleExpanded();
ensureRowsAreVisible(row, row + Math.min
(10, node.visibleChildCount()));
}
else {
if(childIndex == -1) {
eNode.toggleExpanded();
ensureRowsAreVisible(row, row + Math.min
(10, eNode.getTotalChildCount()));
}
else {
toggleExpandState( row );
LargeTreeModelNode newNode;
newNode = getLargeTreeModelNodeForRow(row, false);
if ( newNode != null ) {
ensureRowsAreVisible(row, row + Math.min
(10, newNode.getTotalChildCount()));
}
}
}
}
protected void toggleExpandState( int row )
{
if ( !isExpanded( row ) )
{
expandRow( row );
}
else
{
collapseRow( row );
}
}
/**
* Invoked when a mouse button has been released on a component.
*/
public void mouseReleased(MouseEvent e) {
}
/**
* Invoked when the mouse enters a component.
*/
public void mouseEntered(MouseEvent e) {
}
/**
* Invoked when the mouse exits a component.
*/
public void mouseExited(MouseEvent e) {
}
/**
* Invoked when focus is activated on the tree we're in, redraws the
* lead row.
*/
public void focusGained(FocusEvent e) {
if(tree != null) {
int leadRow = tree.getLeadSelectionRow();
if(leadRow != -1)
tree.repaint(getRowBounds(leadRow));
}
}
/**
* Invoked when focus is activated on the tree we're in, redraws the
* lead row.
*/
public void focusLost(FocusEvent e) {
focusGained(e);
}
public void keyPressed(KeyEvent e) {
if(tree.hasFocus() && tree.isEnabled()) {
KeyStroke keyStroke = KeyStroke.getKeyStroke
(e.getKeyCode(), e.getModifiers());
if(tree.getConditionForKeyStroke(keyStroke) ==
JComponent.WHEN_FOCUSED) {
ActionListener listener = tree.
getActionForKeyStroke(keyStroke);
if(listener instanceof Action)
repeatKeyAction = (Action)listener;
else
repeatKeyAction = null;
}
else
repeatKeyAction = null;
if(isKeyDown && repeatKeyAction != null) {
repeatKeyAction.actionPerformed(null);
e.consume();
}
else
isKeyDown = true;
}
}
public void keyReleased(KeyEvent e) {
isKeyDown = false;
}
public void keyTyped(KeyEvent e) {
}
// Following are subclassed to stopEditing before passing to super.
/**
* Stops editing and messages super.
*/
public void setModel(TreeModel newModel) {
completeEditing();
super.setModel(newModel);
}
/**
* Stops editing and messags super.
*/
public void rebuild() {
completeEditing();
super.rebuild();
}
/**
* Stops editing, messags super and becomes a listener on the model.
*/
public void setSelectionModel(TreeSelectionModel newLSM) {
completeEditing();
if(treeSelectionModel != null)
treeSelectionModel.removePropertyChangeListener(this);
super.setSelectionModel(newLSM);
if(treeSelectionModel != null)
treeSelectionModel.addPropertyChangeListener(this);
if(tree != null)
tree.repaint();
}
/**
* Creates an instance of BasicVisibleTreeNode.
*/
protected VisibleTreeNode createNodeForValue(Object value, int index) {
return new BasicVisibleTreeNode(this, value, index);
}
/**
* Creates an instance of BasicLargeTreeModelNode.
*/
protected LargeTreeModelNode createLargeTreeModelNodeForValue(Object value,
int childIndex) {
return new BasicLargeTreeModelNode(this, value, childIndex);
}
/**
* Repaints the particular node by getting its bounds.
*/
protected void repaintNode(BasicLargeTreeModelNode node) {
if(tree != null)
tree.repaint(0, node.getRow() * rowHeight, tree.getSize().width,
rowHeight);
}
/**
* Repaints the particular node by getting its bounds.
*/
protected void repaintNode(VisibleTreeNode node) {
Rectangle nodeBounds = node.getNodeBounds();
if(tree != null)
tree.repaint(0, nodeBounds.y, tree.getSize().width,
nodeBounds.height);
}
// Serialization support.
private void writeObject(ObjectOutputStream s) throws IOException {
Vector values = new Vector();
s.defaultWriteObject();
// Save the collapsedIcon, if its Serializable.
if(collapsedIcon != null && collapsedIcon instanceof Serializable) {
values.addElement("collapsedIcon");
values.addElement(collapsedIcon);
}
// Save the expandedIcon, if its Serializable.
if(expandedIcon != null && expandedIcon instanceof Serializable) {
values.addElement("expandedIcon");
values.addElement(expandedIcon);
}
// Save the currentCellRenderer, if its Serializable.
if(currentCellRenderer != null &&
currentCellRenderer instanceof Serializable) {
values.addElement("currentCellRenderer");
values.addElement(currentCellRenderer);
}
// Save the cellEditor, if its Serializable.
if(cellEditor != null && cellEditor instanceof Serializable) {
values.addElement("cellEditor");
values.addElement(cellEditor);
}
s.writeObject(values);
}
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
Vector values = (Vector)s.readObject();
int indexCounter = 0;
int maxCounter = values.size();
if(indexCounter < maxCounter && values.elementAt(indexCounter).
equals("collapsedIcon")) {
collapsedIcon = (Icon)values.elementAt(++indexCounter);
indexCounter++;
}
if(indexCounter < maxCounter && values.elementAt(indexCounter).
equals("expandedIcon")) {
expandedIcon = (Icon)values.elementAt(++indexCounter);
indexCounter++;
}
if(indexCounter < maxCounter && values.elementAt(indexCounter).
equals("currentCellRenderer")) {
currentCellRenderer = (TreeCellRenderer)values
.elementAt(++indexCounter);
indexCounter++;
}
if(indexCounter < maxCounter && values.elementAt(indexCounter).
equals("cellEditor")) {
cellEditor = (TreeCellEditor)values.elementAt(++indexCounter);
indexCounter++;
}
if(largeModel && tree != null && componentListener == null) {
componentListener = new ComponentAdapter() {
public void componentMoved(ComponentEvent e) {
validCachedPreferredSize = false;
}
};
tree.addComponentListener(componentListener);
}
}
/** TreeTraverseAction is the action used for left/right keys.
* Will toggle the expandedness of a node, as well as potentially
* incrementing the selection.
*/
private class TreeTraverseAction extends AbstractAction implements Serializable {
/** Determines direction to traverse, 1 means expand, -1 means
* collapse. */
private int direction;
public TreeTraverseAction(int direction, String name)
{
super(name);
this.direction = direction;
}
public void actionPerformed(ActionEvent e)
{
int rowCount;
if(tree != null && (rowCount = getRowCount()) > 0)
{
int minSelIndex = tree.getMinSelectionRow();
int newIndex;
if(minSelIndex == -1)
newIndex = 0;
else {
/* Try and expand the node, otherwise go to next
node. */
if(direction == 1) {
if(!isLeaf(minSelIndex) && !isExpanded(minSelIndex)) {
expandRow(minSelIndex);
newIndex = -1;
}
else
newIndex = Math.min(minSelIndex + 1, rowCount - 1);
}
/* Try to collapse node. */
else {
if(!isLeaf(minSelIndex) && isExpanded(minSelIndex)) {
collapseRow(minSelIndex);
newIndex = -1;
}
else {
try
{
if(!largeModel)
newIndex =((VisibleTreeNode)
getNode(minSelIndex)
.getParent()).getRow();
else {
int[] cIndex = new int[1];
LargeTreeModelNode parent;
parent = getLargeParentAndChildIndexOfRow
(minSelIndex, cIndex);
newIndex = parent.getRow();
}
}
catch (Exception exception) {
newIndex = -1;
}
}
}
}
if(newIndex != -1) {
tree.setSelectionInterval(newIndex, newIndex);
ensureRowsAreVisible(newIndex, newIndex);
}
}
}
public boolean isEnabled() { return (tree != null && tree.isEnabled()); }
} // BasicTreeUI.TreeTraverseAction
/** TreePageAction handles page up and page down events.
*/
private class TreePageAction extends AbstractAction implements Serializable {
/** Specifies the direction to adjust the selection by. */
private int direction;
public TreePageAction(int direction, String name) {
super(name);
this.direction = direction;
}
public void actionPerformed(ActionEvent e) {
int rowCount;
if(tree != null && (rowCount = getRowCount()) > 0 &&
treeSelectionModel != null) {
Dimension maxSize = tree.getSize();
int newIndex;
int selIndex;
Rectangle selRect;
Rectangle visRect = tree.getVisibleRect();
int maxVisY = Math.max(0, maxSize.height -
visRect.height);
if(direction == -1)
selIndex = getMinSelectionRow();
else
selIndex = getMaxSelectionRow();
if(selIndex != -1) {
selRect = getRowBounds(selIndex);
if(direction == -1) {
/* Try to scroll the currently selected row to
the bottom of the screen. */
visRect.y = Math.max(0, selRect.y + selRect.height -
visRect.height);
}
else {
/* Try to scroll the currently selected row to
the top of the screen. */
visRect.y = Math.min(maxVisY, selRect.y);
}
}
else {
/* Scroll a whole page size. */
visRect.y = Math.min(maxVisY, visRect.y + visRect.height *
direction);
}
if(direction == 1) {
newIndex = getClosestRowForLocation(visRect.x, visRect.y +
visRect.height - 1);
selRect = getRowBounds(newIndex);
visRect.y = Math.min(maxVisY, (selRect.y + selRect.height)-
visRect.height);
}
else {
newIndex = getClosestRowForLocation(visRect.x,
visRect.y + 1);
selRect = getRowBounds(newIndex);
visRect.y = Math.min(maxVisY, selRect.y);
}
tree.scrollRectToVisible(visRect);
tree.setSelectionRow(newIndex);
}
}
public boolean isEnabled() { return (tree != null && tree.isEnabled()); }
} // BasicTreeUI.TreePageAction
/** TreeIncrementAction is used to handle up/down actions. Selection
* is moved up or down based on direction.
*/
private class TreeIncrementAction extends AbstractAction implements
Serializable {
/** Specifies the direction to adjust the selection by. */
private int direction;
public TreeIncrementAction(int direction, String name)
{
super(name);
this.direction = direction;
}
public void actionPerformed(ActionEvent e)
{
int rowCount;
if(tree != null && treeSelectionModel != null &&
(rowCount = getRowCount()) > 0) {
int minSelIndex = tree.getMaxSelectionRow();
int newIndex;
if(minSelIndex == -1) {
if(direction == 1)
newIndex = 0;
else
newIndex = rowCount - 1;
}
else
/* Aparently people don't like wrapping;( */
newIndex = Math.min(rowCount - 1, Math.max
(0, (minSelIndex + direction)));
tree.setSelectionInterval(newIndex, newIndex);
ensureRowsAreVisible(newIndex, newIndex);
}
}
public boolean isEnabled() { return (tree != null && tree.isEnabled()); }
} // End of class BasicTreeUI.TreeIncrementAction
/**
* TreeHomeAction is used to handle end/home actions.
* Scrolls either the first or last cell to be visible based on
* direction.
*/
private class TreeHomeAction extends AbstractAction implements
Serializable {
private int direction;
public TreeHomeAction(int direction, String name) {
super(name);
this.direction = direction;
}
public void actionPerformed(ActionEvent e) {
int rowCount = getRowCount();
if(tree != null && rowCount > 0) {
if(direction == -1) {
ensureRowsAreVisible(0, 0);
tree.setSelectionInterval(0, 0);
}
else {
ensureRowsAreVisible(rowCount - 1, rowCount - 1);
tree.setSelectionInterval(rowCount - 1, rowCount - 1);
}
}
}
public boolean isEnabled() { return (tree != null && tree.isEnabled()); }
} // End of class BasicTreeUI.TreeHomeAction
/**
* For the first selected row expandedness will be toggled.
*/
private class TreeToggleAction extends AbstractAction implements
Serializable {
public TreeToggleAction(String name) {
super(name);
}
public void actionPerformed(ActionEvent e) {
if(tree != null) {
int selRow = tree.getMinSelectionRow();
if(selRow != -1 && !isLeaf(selRow)) {
if(isExpanded(selRow))
collapseRow(selRow);
else
expandRow(selRow);
}
}
}
public boolean isEnabled() { return (tree != null && tree.isEnabled()); }
} // End of class BasicTreeUI.TreeToggleAction
/**
* BasicTreeMouseListener handles passing all mouse events,
* including mouse motion events, until the mouse is released to
* the destination it is constructed with. It is assumed all the
* events are currently target at source.
*/
// PENDING(scott): this could actually be moved into a general
// location, no reason to be in here.
public class BasicTreeMouseListener extends Object implements
MouseListener, MouseMotionListener, Serializable
{
/** Source that events are coming from. */
protected Component source;
/** Destination that recieves all events. */
protected Component destination;
public BasicTreeMouseListener(Component source, Component destination,
MouseEvent event){
this.source = source;
this.destination = destination;
this.source.addMouseListener(this);
this.source.addMouseMotionListener(this);
/* Dispatch the editing event! */
destination.dispatchEvent(SwingUtilities.convertMouseEvent
(source, event, destination));
}
public void mouseClicked(MouseEvent e) {
if(destination != null)
destination.dispatchEvent(SwingUtilities.convertMouseEvent
(source, e, destination));
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
if(destination != null)
destination.dispatchEvent(SwingUtilities.convertMouseEvent
(source, e, destination));
removeFromSource();
}
public void mouseEntered(MouseEvent e) {
removeFromSource();
}
public void mouseExited(MouseEvent e) {
removeFromSource();
}
public void mouseDragged(MouseEvent e) {
removeFromSource();
}
public void mouseMoved(MouseEvent e) {
removeFromSource();
}
protected void removeFromSource() {
if(source != null) {
source.removeMouseListener(this);
source.removeMouseMotionListener(this);
}
source = destination = null;
}
} // End of class BasicTreeUI.BasicTreeMouseListener
/**
* BasicTreeUIPaintInfo is used during a painting session for largeModels.
*/
public static class BasicTreeUIPaintInfo {
public Graphics g;
public TreeCellRenderer renderer;
public int totalIndent;
public Color hashColor;
public int halfRowHeight;
public int levelOffset;
public Icon expandedIcon;
public Icon collapsedIcon;
public int rChildIndent;
public int rowHeight;
public JTree tree;
public TreeModel treeModel;
public int leadIndex;
public BasicTreeUIPaintInfo(BasicTreeUI ui, Graphics g) {
reset(ui, g);
}
public void reset(BasicTreeUI ui, Graphics g) {
this.g = g;
this.renderer = ui.getCellRenderer();
this.totalIndent = ui.totalChildIndent;
this.hashColor = ui.getHashColor();
this.halfRowHeight = ui.getRowHeight() / 2;
if(ui.getShowsRootHandles())
this.levelOffset = 1;
else
this.levelOffset = 0;
this.expandedIcon = ui.getExpandedIcon();
this.collapsedIcon = ui.getCollapsedIcon();
this.rChildIndent = ui.getRightChildIndent();
this.rowHeight = ui.getRowHeight();
this.tree = ui.tree;
this.treeModel = ui.getModel();
if(!ui.tree.hasFocus())
this.leadIndex = -1;
else
this.leadIndex = ui.tree.getLeadSelectionRow();
}
} // End of class BasicTreeUI.BasicTreeUIPaintInfo
} // End of class BasicTreeUI